﻿#pragma once

#include "World/Chunk.h"
#include "World/WorldGenerator.h"
#include "Render/FrameBuffer.h"
#include "Render/Atlas.h"
#include "Save/Persistence.h"
#include "Scene/Behavior/WorldBehavior.h"
#include "Util/Hash.h"

class Skybox;
class Level;

class CORE_API World
{
public:
  using ChunkPositions = std::vector<std::pair<glm::ivec2, float>>;

  World(Level &level, const Ref<Persistence> &persistence, std::vector<Ref<WorldBehavior>> behaviors);

  Ref<Chunk> LoadChunk(const glm::ivec2 &worldPosition);
  Ref<Chunk> GetChunk(const glm::ivec2 &worldPosition) const;
  void UnloadChunk(const Ref<Chunk> &chunk);
  BlockData *GetBlockAt(const glm::ivec3 &worldPosition);
  BlockData *GetBlockAtIfLoaded(const glm::ivec3 &worldPosition) const;

  bool IsChunkLoaded(const glm::ivec2 &worldPosition) const;
  static glm::ivec2 GetChunkIndex(const glm::ivec3 &worldPosition);

  void SetViewDistance(int distance);
  int GetViewDistance() const { return m_viewDistance; }

  bool PlaceBlock(const glm::ivec3 &worldPosition, BlockData blockData, Player *player = nullptr);

  void SortChunkIndices(const glm::vec3 &playerPos, const Ref<ChunkPositions> &index);
  void RebuildChunkMeshes(const Ref<ChunkPositions> &chunkIndices);

  static glm::ivec3 ToPositionInChunk(const glm::ivec3 &worldPosition);

  void Update(const glm::vec3 &playePos, float deltaTime);
  void RenderOpaque(const Ref<Camera> &camera, const glm::vec3 &playePos);
  void RenderDeferredLight(const Ref<Camera> &camera,
                           glm::vec3 playerPos,
                           Skybox &skybox,
                           Ref<FrameBuffer> blendRender,
                           Ref<FrameBuffer> opaqueRender);
  void RenderDeferredLightTransparent(const Ref<Camera> &camera,
                           glm::vec3 playerPos,
                           Skybox &skybox,
                           Ref<FrameBuffer> blendRender,
                           Ref<FrameBuffer> opaqueRender);
  void RenderTransparent(const Ref<Camera> &camera,
                         glm::vec3 playerPos,
                         Ref<FrameBuffer> lightedRender,
                         Ref<FrameBuffer> opaqueRender);

  Ref<WorldGenerator> GetWorldGenerator() const { return m_worldGenerator; }

  Ref<Atlas> GetAtlas() const { return m_atlas; }

  Level &GetLevel() const { return m_level; }

  std::unordered_map<glm::ivec2, Ref<Chunk>, Util::HashIVec2> chunks;

#ifdef NDEBUG
  const uint32_t MaxRebuildsAllowedPerFrame = 10;
#else
  const uint32_t MaxRebuildsAllowedPerFrame = 1;
#endif

  float textureAnimation = 0;
  static constexpr float TextureAnimationSpeed = 4;

protected:
  Ref<WorldGenerator> m_worldGenerator{nullptr};
  Ref<Persistence> m_persistence{nullptr};
  Ref<Atlas> m_atlas{nullptr};

  std::vector<Ref<WorldBehavior>> m_behaviors{};

  Ref<Shader> m_opaqueShader{nullptr};
  Ref<Shader> m_transparentShader{nullptr};
  Ref<Shader> m_blendShader{nullptr};

  Ref<FrameBuffer> m_frameBuffer{nullptr};

  Level &m_level;

  int m_viewDistance = 6;

  friend class Chunk;
};